package io.plus.engine.pulse.collections;

import io.plus.engine.pulse.AsynchronousSocketChannelContext;
import io.plus.engine.pulse.Configuration;
import io.plus.engine.utils.lock.LockUtils;
import io.plus.engine.utils.lock.ReadWriteLockMap;
import io.plus.engine.utils.lock.ReadWriteLockSet;
import lombok.extern.slf4j.Slf4j;
import tool.plus.$;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

/**
 * 一对多  (userId <--> AsynchronousSocketChannelContext)<br>
 */
@Slf4j
public class Users {

    /**
     * key: userId
     * value: AsynchronousSocketChannelContext
     */
    private ReadWriteLockMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>> userIdToChannelContextSetMap = new ReadWriteLockMap<>(new HashMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>>());

    /**
     * 绑定userid.
     */
    public void bind(String userId, AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        if (asynchronousSocketChannelContext.clientConfiguration.isShortConnection) {
            return;
        }

        if ($.isNullOrEmpty(userId)) {
            return;
        }

        try {
            ReadWriteLockSet<AsynchronousSocketChannelContext> channelContextSet = userIdToChannelContextSetMap.get(userId);
            if (channelContextSet == null) {
                LockUtils lockUtils = new LockUtils();
                lockUtils.runWriteOrWaitRead("io_users_bind_" + userId, this, () -> {
                    if (userIdToChannelContextSetMap.get(userId) == null) {
                        userIdToChannelContextSetMap.put(userId, new ReadWriteLockSet<>(new HashSet<AsynchronousSocketChannelContext>()));
                    }
                });
                channelContextSet = userIdToChannelContextSetMap.get(userId);
            }
            channelContextSet.add(asynchronousSocketChannelContext);

            asynchronousSocketChannelContext.setUserId(userId);
        } catch (Throwable e) {
            log.error("", e);
        }

    }

    public ReadWriteLockSet<AsynchronousSocketChannelContext> find(Configuration configuration, String userId) {
        if (configuration.isShortConnection) {
            return null;
        }

        if ($.isNullOrEmpty(userId)) {
            return null;
        }

        return userIdToChannelContextSetMap.get(userId);
    }

    public ReadWriteLockMap<String, ReadWriteLockSet<AsynchronousSocketChannelContext>> getUserIdToChannelContextSetMap() {
        return userIdToChannelContextSetMap;
    }

    /**
     * 解除channelContext绑定的userid
     */
    public void unbind(AsynchronousSocketChannelContext asynchronousSocketChannelContext) {
        if (asynchronousSocketChannelContext.clientConfiguration.isShortConnection) {
            return;
        }

        String userId = asynchronousSocketChannelContext.userId;
        if ($.isNullOrEmpty(userId)) {
            log.debug("{}, {}, 并没有绑定用户", asynchronousSocketChannelContext.clientConfiguration.getName(), asynchronousSocketChannelContext.toString());
            return;
        }

        try {
            ReadWriteLockSet<AsynchronousSocketChannelContext> channelContextSet = userIdToChannelContextSetMap.get(userId);
            if (channelContextSet == null) {
                log.warn("{}, {}, userId:{}, 没有找到对应的SetWithLock", asynchronousSocketChannelContext.clientConfiguration.getName(), asynchronousSocketChannelContext.toString(), userId);
                return;
            }

            channelContextSet.remove(asynchronousSocketChannelContext);

            if (channelContextSet.size() == 0) {
                userIdToChannelContextSetMap.remove(userId);
            }

            asynchronousSocketChannelContext.setUserId(null);
        } catch (Throwable e) {
            log.error(e.toString(), e);
        }
    }

    /**
     * 解除tioConfig范围内所有ChannelContext的 userid绑定
     */
    public void unbind(Configuration configuration, String userId) {
        if (configuration.isShortConnection) {
            return;
        }
        if ($.isNullOrEmpty(userId)) {
            return;
        }

        try {
            Lock lock = userIdToChannelContextSetMap.getWriteLock();
            lock.lock();
            try {
                Map<String, ReadWriteLockSet<AsynchronousSocketChannelContext>> userIdToChannelContextSetMap = this.userIdToChannelContextSetMap.getObject();
                ReadWriteLockSet<AsynchronousSocketChannelContext> channelContextSet = userIdToChannelContextSetMap.get(userId);
                if (channelContextSet == null) {
                    return;
                }

                WriteLock writeLock = channelContextSet.getWriteLock();
                writeLock.lock();
                try {
                    Set<AsynchronousSocketChannelContext> $AsynchronousSocketChannelContextSet = channelContextSet.getObject();
                    if ($AsynchronousSocketChannelContextSet.size() > 0) {
                        for (AsynchronousSocketChannelContext asynchronousSocketChannelContext : $AsynchronousSocketChannelContextSet) {
                            asynchronousSocketChannelContext.setUserId(null);
                        }
                        $AsynchronousSocketChannelContextSet.clear();
                    }

                    userIdToChannelContextSetMap.remove(userId);
                } catch (Throwable e) {
                    log.error(e.getMessage(), e);
                } finally {
                    writeLock.unlock();
                }

            } catch (Throwable e) {
                throw e;
            } finally {
                lock.unlock();
            }
        } catch (Throwable e) {
            log.error(e.toString(), e);
        }
    }
}
